package server;

import java.sql.SQLException;

public class Commands {
	private MYSQLComm mysql;
	private ConfigurationManager config;

	/**
	 * Initializes the Commands class using the given ConfigurationManager.
	 *
	 * @param config    The ConfigurationManager to use
	 *
	 * @throws SQLException on SQL error
	 * @throws Exception    on any other error
	 */
	public Commands(ConfigurationManager config) throws SQLException, Exception
	{
		this.config = config;
		mysql = new MYSQLComm(
				config.getDBIP(), 
				config.getDBName(), 
				config.getDBUser(), 
				config.getDBPassword(), 
				true);
		addGlobalLog("Firewall Command Manager started");
	}

	//add an init method (for bootup).
	//should we check safteyinput of params? (like one type of rule, etc..)
	//why add an option for disabling log at all?

	// an exception here might invalidate the state.

	/**
	 * Switches the active ruleset to the one given.
	 *
	 * @throws FirewallException on any error.
	 */
	private void switchRuleSet(String name) throws FirewallException
	{
		try {
			IPTablesComm.flush();
			Rule[] InRules = mysql.getRules(getTableName(RulesTableType.IN, name));
			Rule[] OutRules = mysql.getRules(getTableName(RulesTableType.OUT, name));
			for (Rule rule : InRules)
			{
				addRule(RulesTableType.IN, name, rule, false);
			}
			for (Rule rule : OutRules)
			{
				addRule(RulesTableType.OUT, name, rule, false);
			}
		} catch (SQLException e) {
			addGlobalLog("Failed switching ruleset to " + name + 
					": " + e.getMessage());
			throw new FirewallException(e.getMessage());
		} catch (FirewallException e) {
			addGlobalLog("Failed switching ruleset to " + name + 
					": " + e.getMessage());
			throw e;
		}
		addGlobalLog("Switched ruleset to " + name);
	}

	/**
	 * @return The table name for the ruleset + direction
	 */
	private String getTableName(RulesTableType dir, String ruleset)
	{
		String ret;
		if (ruleset.compareToIgnoreCase("default") == 0)
			ret = "ActiveRules_";
		else
			ret = "RuleSet_" + ruleset + "_";
		ret += (dir == RulesTableType.IN) ? "In" : "Out";	
		return ret;
	}

	/**
	 * Turns on the firewall.
	 *
	 * @throws FirewallException on any error.
	 */
	public synchronized void turnOn() throws FirewallException
	{
		addGlobalLog("Turning on firewall");
		switchRuleSet(getCurrentRuleSet());
	}

	/**
	 * Turns off the firewall.
	 *
	 * @throws FirewallException on any error.
	 */
	public synchronized void turnOff() throws FirewallException
	{
		try {
			IPTablesComm.flush();
		} catch (FirewallException e) {
			addGlobalLog("Failed turning off firewall: " + e.getMessage());
			throw e;
		}
		addGlobalLog("Turning off firewall");
	}

	/// @return 'true' if log enabled, 'false' if not.
	public synchronized boolean isLogEnabled()
	{
		return config.getIsLogActive();
	}

	/**
	 * Enables logging for all active rules.
	 * Unloads all rules, then reloads them with logging enabled.
	 *
	 * @throws FirewallException on any error
	 */
	public synchronized void enableLog() throws FirewallException
	{
		boolean old = isLogEnabled();
		config.setIsLogActive(true);
		try {
			turnOn();
		} catch (FirewallException e) {
			addGlobalLog("Failed enabling log: " + e.getMessage());
			config.setIsLogActive(old);
			throw e;
		}

		addGlobalLog("Enabled log");
	}

	/**
	 * Disables logging for all active rules.
	 * Unloads all rules, then reloads them with logging disabled.
	 *
	 * @throws FirewallException on any error
	 */
	public synchronized void disableLog() throws FirewallException
	{
		boolean old = isLogEnabled();
		config.setIsLogActive(false);
		try {
			turnOn();
		} catch (FirewallException e) {
			addGlobalLog("Failed disabling log: " + e.getMessage());
			config.setIsLogActive(old);
			throw e;
		}

		addGlobalLog("Disabled log");
	}

	/**
	 * Checks if the provided username and password match, and returns the
	 * user's info if they do.
	 *
	 * @param   username    username
	 * @param   password    password
	 *
	 * @throws InvalidLoginException if username doesn't match password, or if
	 *                               the user doesn't exist
	 * @throws FirewallException     on any other error
	 */
	public synchronized Password checkUser(String username, String password) throws InvalidLoginException, FirewallException
	{
		Password info;
		try {
			info = mysql.getPassword(username);
			if (info.password.compareTo(password) != 0) {
				addGlobalLog("Invalid login attempt for " + username);
				throw new InvalidLoginException();
			}
			addGlobalLog(username + " logged in");
			return info;
		} catch (SQLException e) {
			if (e.getMessage().compareTo("No such user") == 0) {
				addGlobalLog("Invalid login - no such user " + username);
				throw new InvalidLoginException();
			} else {
				addGlobalLog("Error logging in: " + e.getMessage());
				throw new FirewallException(e.getMessage());
			}
		}
	}

	/**
	 * Adds the given rule to the specified ruleset, in the given direction.
	 *
	 * @param   dir     Direction to use
	 * @param   ruleset Ruleset to use
	 * @param   rule    The rule to add
	 *
	 * @throws FirewallException on any error
	 */
	public synchronized void addRule(RulesTableType dir, String ruleset, Rule rule, boolean updatedb) throws FirewallException
	{
		Protocol protocol;
		String regexp = null;
		if (rule.type == Rule.typeOpt.CONTEXT)
		{
			addGlobalLog(
					"Adding context rule " + rule.index + 
					" port " + rule.port + 
					" protocol " + rule.protocol + 
					" direction " + ((dir == RulesTableType.IN) ? "in" : "out")
			);
			try {
				protocol = mysql.getProtocol(rule.protocol);
			} catch (SQLException e) {
				addGlobalLog("Failed adding rule: " + e.getMessage());
				throw new FirewallException(e.getMessage());
			}
			if (dir == RulesTableType.IN)
				regexp = protocol.inReg;
			else
				regexp = protocol.outReg;
			if (regexp == null)
                throw new FirewallException("No regexp defined for this direction in the protocol.");
		} else {
			addGlobalLog(
					"Adding IP rule " + rule.index + 
					" port " + rule.port + 
					" IP range " + rule.iprange + 
					" direction " + ((dir == RulesTableType.IN) ? "in" : "out")
			);
		}

		// Add rule to DB
		if (updatedb)
        {
    		try {
    			mysql.addRule(getTableName(dir, ruleset), rule);
    		} catch (SQLException e) {
    			addGlobalLog("Failed adding rule: " + e.getMessage());
    			throw new FirewallException(e.getMessage());
    		}
        }

		if (getCurrentRuleSet().compareTo(ruleset) == 0)
		{
			// And now to IPTables
			try {
				IPTablesComm.add(dir, rule, isLogEnabled(), regexp);
			} catch (FirewallException e) {
				addGlobalLog("Failed adding rule: " + e.getMessage());
				if (updatedb)
				{
    				try {
    					mysql.deleteRule(getTableName(dir, ruleset), rule.index);
    					mysql.deleteRuleComplete(true);
    				} catch (SQLException ex) {
    					try {
    						mysql.deleteRuleComplete(false);
    					} catch (SQLException ex1) {
    						throw new FirewallException(ex1.getMessage());					
    					}
    					throw new FirewallException(ex.getMessage());
    				}
    			}
				throw e;
			}
		}
		addGlobalLog("Rule added successfully");
	}

	// do rollback & commit support in SQL. implment this that way.
	/**
	 * Deletes the rule by the given index, in the specified 
	 * ruleset and direction.
	 *
	 * @param   dir     Direction
	 * @param   ruleset Ruleset to use
	 * @param   index   Rule index to delete
	 * 
	 * @throws  FirewallException   on any error
	 */
	public synchronized void deleteRule(RulesTableType dir, String ruleset, int index) throws FirewallException
	{
		try {
			mysql.deleteRule(getTableName(dir, ruleset), index);
			if (getCurrentRuleSet().compareTo(ruleset) == 0)
			{
				IPTablesComm.remove(dir, index);
			}
			mysql.deleteRuleComplete(true);
		} catch (SQLException e) {
			try {
				mysql.deleteRuleComplete(false);
			} catch (SQLException ex) {
				throw new FirewallException(ex.getMessage());
			}
			addGlobalLog("Failed deleting rule: " + e.getMessage());
			throw new FirewallException(e.getMessage());
		}
		addGlobalLog("Deleted rule " + index + ", direction " + 
				((dir == RulesTableType.IN) ? "in" : "out"));
	}

	/**
	 * Retrieves all rules in the given ruleset.
	 *
	 * @param   dir     Direction to use
	 * @param   ruleset Ruleset to retrieve rules from
	 *
	 * @return  Array of all rules in ruleset
	 *
	 * @throws FirewallException    on any error
	 */
	public synchronized Rule[] getRules(RulesTableType dir, String ruleset) throws FirewallException
	{
		try {
			return mysql.getRules(getTableName(dir, ruleset));
		} catch (SQLException e) {
			addGlobalLog("Failed retrieving rules: " + e.getMessage());
			throw new FirewallException(e.getMessage());
		}		
	}

	/**
	 * Adds the given protocol to the protocols list
	 *
	 * @param   protocol    protocol to add
	 *
	 * @throws FirewallException    on any error
	 */
	public synchronized void addProtocol(Protocol protocol) throws FirewallException
	{
		try {
			mysql.addProtocol(protocol);
		} catch (SQLException e) {
			addGlobalLog("Failed adding protocol " + protocol.protocol + 
					": " + e.getMessage());
			throw new FirewallException(e.getMessage());
		}	
	}

	/**
	 * Deletes the given protocol from the protocols list
	 *
	 * @param   name    name of protocol to delete
	 *
	 * @throws FirewallException    on any error
	 */
	public synchronized void deleteProtocol(String name) throws FirewallException
	{
		try {
			mysql.deleteProtocol(name);
		} catch (SQLException e) {
			addGlobalLog("Failed deleting protocol " + name + 
					": " + e.getMessage());
			throw new FirewallException(e.getMessage());
		}
		addGlobalLog("Protocol " + name + " added");
	}

	/**
	 * Updates the given protocol to the protocols list
	 *
	 * @param   protocol    protocol to update
	 *
	 * @throws FirewallException    on any error
	 */
	public synchronized void updateProtocol(Protocol protocol) throws FirewallException
	{
		try {
			mysql.updateProtocol(protocol);
		} catch (SQLException e) {
			addGlobalLog("Failed updating protocol " + protocol.protocol + 
					": " + e.getMessage());
			throw new FirewallException(e.getMessage());
		}	
		addGlobalLog("Protocol " + protocol.protocol + " updated");
	}

	/**
	 * Retrieves all protocols defined in the system.
	 *
	 * @return  Array of all defined protocols.
	 *
	 * @throws FirewallException    on any error
	 */
	public synchronized Protocol[] getProtocols() throws FirewallException
	{
		try {
			return mysql.getProtocols();
		} catch (SQLException e) {
			addGlobalLog("Failed retrieving protocols: " + e.getMessage());
			throw new FirewallException(e.getMessage());
		}		
	}

	/**
	 * Creates a new ruleset, containing all the currently active rules.
	 *
	 * @param   name    Name of new ruleset to create.
	 *
	 * @throws FirewallException    on any error
	 */
	public synchronized void createRuleSet(String name) throws FirewallException
	{
		try {
			mysql.createRuleSet(name);
		} catch (SQLException e) {
			addGlobalLog("Failed creating ruleset " + name + 
					": " + e.getMessage());
			throw new FirewallException(e.getMessage());
		}	
		addGlobalLog("Ruleset " + name + " created");
	}

	/**
	 * Deletes a given ruleset from the database.
	 * Don't do this while it's the active ruleset...
	 *
	 * @param   name    Name of ruleset to delete.
	 *
	 * @throws FirewallException    on any error
	 */
	public synchronized void deleteRuleSet(String name) throws FirewallException
	{
		try {
			mysql.deleteProtocol(name);
		} catch (SQLException e) {
			addGlobalLog("Failed deleting ruleset " + name + 
					": " + e.getMessage());
			throw new FirewallException(e.getMessage());
		}	

		addGlobalLog("Ruleset " + name + " deleted");
	}


	/**
	 * Retrieves all rulesets in the system.
	 *
	 * @return  Array of all rulesets in the system.
	 *
	 * @throws FirewallException    on any error
	 */	
	public synchronized String[] getRuleSets() throws FirewallException
	{
		try {
			return mysql.getRuleSets();
		} catch (SQLException e) {
			addGlobalLog("Failed retreiving rulesets: " + e.getMessage());
			throw new FirewallException(e.getMessage());
		}	
	}

	/**
	 * Sets a new active ruleset. All rules in this rulesets are applied.
	 *
	 * @param   name    Name of ruleset to activate
	 *
	 * @throws FirewallException    on any error
	 */	
	public synchronized void setRuleSet(String name) throws FirewallException
	{
		String old = getCurrentRuleSet();
		try {
			config.setActiveRuleset(name);
			switchRuleSet(name);
		} catch (FirewallException e) {

			// error - try to undo

			addGlobalLog("Failed setting ruleset to " + name + 
					": " + e.getMessage());
			config.setActiveRuleset(old);
			switchRuleSet(old);
		}
		addGlobalLog("Set ruleset to " + name);
	}

	/// @return name of active ruleset
	public synchronized String getCurrentRuleSet()
	{
		return config.getActiveRuleset();
	}

	/**
	 * Retrieves log messages from the specified time period.
	 *
	 * @param   days    Log messages will be retrieved this many days back
	 *
	 * @return  Array of log messages in specified time period.
	 *
	 * @throws FirewallException    on any error
	 */	
	public synchronized Log[] getLogs(int days) throws FirewallException
	{
		try {
			return mysql.getLogs(days);
		} catch (SQLException e) {
			addGlobalLog("Failed getting logs: " + e.getMessage());
			throw new FirewallException(e.getMessage());
		}	
	}

	/**
	 * Retrieves log messages from the specified time period, by IP address,
	 * port, and direction.
	 *
	 * @param   days    Log messages will be retrieved this many days back
	 * @param   address IP range to use (can be null, to use any address)
	 * @param   port    Port to use
	 * @param   dir     Direction to use
	 *
	 * @return  Array of matching log messages
	 *
	 * @throws FirewallException    on any error
	 */	
	public synchronized Log[] getLogs(int days, String address, Integer port, Log.directOpt dir) throws FirewallException
	{
		return null;	
	}

	/**
	 * Retrieves log messages from the specified time period, for the given
	 * protocol.
	 *
	 * @param   days        Log messages will be retrieved this many days back
	 * @param   protocol    Protocol to use
	 *
	 * @return  Array of matching log messages
	 *
	 * @throws FirewallException    on any error
	 */	
	public synchronized Log[] getLogs(int days, String protocol) throws FirewallException
	{
		return null;
	}

	/**
	 * Retrieves global log messages from the specified time period.
	 *
	 * @param   days        Log messages will be retrieved this many days back
	 *
	 * @return  Array of matching log messages
	 *
	 * @throws FirewallException    on any error
	 */	
	public synchronized GlobalLog[] getGlobalLogs(int days) throws FirewallException
	{
		try {
			return mysql.getGlobalLogs(days);
		} catch (SQLException e) {
			addGlobalLog("Failed getting global logs: " + e.getMessage());
			throw new FirewallException(e.getMessage());
		}	
	}

	/**
	 * Stores a new global log message.
	 *
	 * @param   msg Message to log
	 *
	 * @throws FirewallException on any error
	 */   
	public synchronized void addGlobalLog(String msg) throws FirewallException
	{
		try {
		    System.out.println(msg);
			mysql.addGlobalLog(msg);
		} catch (SQLException e) {
			throw new FirewallException(e.getMessage());
		}	
	}	
}
